/*
 * Copyright 2007-2009 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package net.paoding.rose.web.impl.module;

import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import net.paoding.rose.RoseConstants;
import net.paoding.rose.impl.scanning.ModuleScanner;
import net.paoding.rose.impl.util.SpringUtils;
import net.paoding.rose.web.ControllerErrorHandler;
import net.paoding.rose.web.ControllerInterceptor;
import net.paoding.rose.web.annotation.Ignored;
import net.paoding.rose.web.annotation.Interceptor;
import net.paoding.rose.web.annotation.ReqMapping;
import net.paoding.rose.web.annotation.ReqMethod;
import net.paoding.rose.web.paramresolver.Resolver;
import net.paoding.rose.web.paramresolver.ResolverInstances;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.context.ApplicationContext;
import org.springframework.core.io.UrlResource;
import org.springframework.util.ClassUtils;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.XmlWebApplicationContext;
import org.springframework.web.multipart.MultipartResolver;
import org.springframework.web.multipart.commons.CommonsMultipartResolver;

/**
 * 根据输入的module类信息，构造出具体的Module结构出来
 * 
 * @author zhiliang.wang [qieqie.wang@paoding.net]
 */
public class ModulesBuilder {

    private Log logger = LogFactory.getLog(getClass());

    public List<Module> build(XmlWebApplicationContext rootContext, ModuleScanner moduleScanner) {
        List<Module> modules = new ArrayList<Module>(moduleScanner.getModulePaths().size());
        for (String modulePath : moduleScanner.getModulePaths()) {
            final List<URL> contextResources = moduleScanner.getContextResouces(modulePath);
            XmlWebApplicationContext context = rootContext;
            if (contextResources.size() != 0) {
                context = new XmlWebApplicationContext() {

                    protected void loadBeanDefinitions(XmlBeanDefinitionReader reader)
                            throws BeansException, IOException {
                        for (URL url : contextResources) {
                            reader.loadBeanDefinitions(new UrlResource(url));
                        }
                    }
                };
                ((XmlWebApplicationContext) context).setParent(rootContext);
                ((XmlWebApplicationContext) context).setServletContext(rootContext
                        .getServletContext());
                ((XmlWebApplicationContext) context).refresh();
            }
            context.getServletContext().setAttribute(
                    WebApplicationContext.class.getName() + ".module_" + modulePath, context);

            // 从Spring应用环境中找出全局共有的拦截器，一会要设进去到各个Module中
            ArrayList<NamedControllerInterceptor> globalInterceptors = findGlobaInterceptors(context);
            // 找出默认的控制器异常处理类，如果模块没有定义自己的异常处理类，则使用它
            ControllerErrorHandler defaultErrorHandler = getDefaultErrorHandler(context);

            ModuleBean module = new ModuleBean(modulePath, (XmlWebApplicationContext) context);

            // multipart resolver
            module.setMultipartResolver(initMultipartResolver(context));

            // 将全局的拦截器设置到module中
            for (NamedControllerInterceptor namedInterceptor : globalInterceptors) {
                module.addNamedControllerInterceptor(namedInterceptor);
            }
            if (logger.isDebugEnabled()) {
                logger.debug("module '" + module.getPath() + "': apply global intercetpors "
                        + Arrays.toString(globalInterceptors.toArray()));
            }

            // 扫描找到的类...
            for (Class<?> clazz : moduleScanner.getModuleClasses(modulePath)) {
                if (Modifier.isAbstract(clazz.getModifiers())
                        || Modifier.isInterface(clazz.getModifiers())
                        || !Modifier.isPublic(clazz.getModifiers())) {
                    if (logger.isDebugEnabled()) {
                        logger.debug("Ignored by its modififer:" + clazz.getName());
                    }
                    continue;
                }
                if (clazz.isAnnotationPresent(Ignored.class)) {
                    if (logger.isDebugEnabled()) {
                        logger.debug("Ignored by annoation @" + Ignored.class.getName() + ":"
                                + clazz.getName());
                    }
                    continue;
                }
                // 在这里try-catche使得一个错误不去影响其它类
                try {
                    checkController(context, module, clazz);
                    checkResolver(context, module, clazz);
                    checkInterceptor(context, module, clazz);
                    checkErrorHandler(context, module, clazz);
                } catch (Throwable e) {
                    logger.error("fail to load/initiate class: " + clazz.getName(), e);
                }
            }
            // 全局resolver (和ResolverInstances缓存的无关,从spring获取的就是自己控制单例，所以不使用ResolverInstances机制)
            List<Resolver> customerResolvers = SpringUtils.getBeans(context, Resolver.class);
            for (Resolver resolver : customerResolvers) {
                module.addCustomerResolver(resolver);
            }
            if (logger.isDebugEnabled()) {
                logger.debug("module '" + module.getPath() + "': apply global resolvers "
                        + Arrays.toString(customerResolvers.toArray()));
            }
            // 
            if (module.getErrorHandler() == null && defaultErrorHandler != null) {
                module.setErrorHandler(defaultErrorHandler);
                if (logger.isInfoEnabled()) {
                    logger.info("set default errorHandler for module: " + module.getPath());
                }
            } else {
                if (logger.isDebugEnabled()) {
                    logger.debug("not found errorHandler for module: " + module.getPath());
                }
            }
            // 放进去以返回
            modules.add(module);
        }
        return modules;
    }

    private ControllerErrorHandler getDefaultErrorHandler(ApplicationContext context) {
        ControllerErrorHandler defaultErrorHandler = null;
        String[] names = context.getBeanNamesForType(ControllerErrorHandler.class);
        for (int i = 0; defaultErrorHandler == null && i < names.length; i++) {
            defaultErrorHandler = (ControllerErrorHandler) context.getBean(names[i]);
            if (defaultErrorHandler.getClass().isAnnotationPresent(Ignored.class)) {
                logger.debug("ignored controllerErrorHandler" + " in application context: "
                        + defaultErrorHandler);
                defaultErrorHandler = null;
            } else {
                break;
            }
        }
        return defaultErrorHandler;
    }

    private ArrayList<NamedControllerInterceptor> findGlobaInterceptors(
            XmlWebApplicationContext context) {
        String[] globalInterceptorNames = SpringUtils.getBeanNames(context.getBeanFactory(),
                ControllerInterceptor.class);
        ArrayList<NamedControllerInterceptor> globalInterceptors = new ArrayList<NamedControllerInterceptor>(
                globalInterceptorNames.length);
        for (String beanName : globalInterceptorNames) {
            ControllerInterceptor interceptor = (ControllerInterceptor) context.getBean(beanName);
            if (interceptor.getClass().isAnnotationPresent(Ignored.class)) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Ignored context interceptor:" + interceptor);
                }
                continue;
            }
            String name = asShortPropertyName(beanName, "Interceptor");
            globalInterceptors.add(new NamedControllerInterceptor(name, interceptor, true));
            if (logger.isDebugEnabled()) {
                logger.debug("context interceptor[" + interceptor.getPriority() + "]: " // \r\n
                        + name + "=" + interceptor.getClass().getName());
            }
        }
        return globalInterceptors;
    }

    protected boolean checkController(ApplicationContext context, ModuleBean module, Class<?> clazz)
            throws InstantiationException, IllegalAccessException {
        if (clazz.getName().endsWith(RoseConstants.CONTROLLER_SUFFIX)) {
            String[] controllerPaths = null;
            ReqMethod[] methods;
            ReqMapping reqMappingAnnotation = clazz.getAnnotation(ReqMapping.class);
            if (reqMappingAnnotation != null) {
                methods = reqMappingAnnotation.methods();
                controllerPaths = reqMappingAnnotation.path();
            } else {
                methods = new ReqMethod[] { ReqMethod.ALL };
            }
            if (controllerPaths != null) {
                // 如果controllerPaths.length==0，表示没有任何path可以映射到这个controller了
                for (int i = 0; i < controllerPaths.length; i++) {
                    if (ReqMapping.DEFAULT_PATH.equals(controllerPaths[i])) {
                        String shortName = ClassUtils.getShortNameAsProperty(clazz);
                        controllerPaths[i] = "/"
                                + shortName.substring(0, shortName.length()
                                        - RoseConstants.CONTROLLER_SUFFIX.length());
                    } else if (controllerPaths[i].length() > 0
                            && controllerPaths[i].charAt(0) != '/') {
                        controllerPaths[i] = '/' + controllerPaths[i];
                    } else if (controllerPaths[i].equals("/")) {
                        controllerPaths[i] = "";
                    }
                }
            } else {
                String shortName = ClassUtils.getShortNameAsProperty(clazz);
                controllerPaths = new String[] { "/"
                        + shortName.substring(0, shortName.length()
                                - RoseConstants.CONTROLLER_SUFFIX.length()) };
            }
            // 这个Controller是否已经在Context中配置了?
            // 如果使用Context配置，就不需要在这里实例化
            Object controller = SpringUtils.getBean(context, clazz);
            Object rawController = null;
            if (controller != null) {
                if (clazz.isAssignableFrom(controller.getClass())) {
                    rawController = controller;
                }
            } else {
                rawController = clazz.newInstance();
                controller = SpringUtils.autowire(rawController, context);
            }
            for (int i = 0; i < controllerPaths.length; i++) {
                if (methods.length > 0) {
                    module.addController(controllerPaths[i], methods, clazz, controller);
                }
            }
            if (Proxy.isProxyClass(controller.getClass())) {
                if (logger.isDebugEnabled()) {
                    logger.debug("module '" + module.getPath() + "': add controller "
                            + Arrays.toString(controllerPaths) + "= proxy of " + clazz.getName());
                }
            } else {
                if (logger.isDebugEnabled()) {
                    logger.debug("module '" + module.getPath() + "': add controller "
                            + Arrays.toString(controllerPaths) + "= "
                            + controller.getClass().getName());
                }
            }
            if (rawController != null) {
                if (controllerPaths.length > 0) {
                    // 实现注意：内部拦截器对控制器字段的引用，最好使用控制器提供getXxx()方法获取，而非直接获取!
                    checkInnerInterceptors(module, clazz, controllerPaths[0], controller,
                            rawController);
                }
            } else {
                if (logger.isDebugEnabled()) {
                    logger.debug("skip checking controller inner intercetpors: " + clazz.getName());
                }
            }
            return true;
        }
        return false;
    }

    private void checkInnerInterceptors(ModuleBean module, Class<?> clazz, String controllerPath,
            Object controller, Object rawController) throws IllegalAccessException {
        // 控制器特有的拦截器
        Class<?> _clazz = clazz;
        while (true) {
            if (_clazz == Object.class || _clazz == null
                    || Modifier.isInterface(_clazz.getModifiers())) {
                break;
            }
            Field[] fields = _clazz.getDeclaredFields();
            for (Field field : fields) {
                // 只对本类声明或父类声明的公共或保护的字段(也就是子类可以引用的字段)
                // !!和控制器是否暴露父类的方法不一样的地方，这里不需要父类声明@AsSuperController标注
                if (clazz == _clazz || Modifier.isPublic(field.getModifiers())
                        || Modifier.isProtected(field.getModifiers())) {
                    if (Modifier.isStatic(field.getModifiers())) {
                        continue;
                    }
                    // 
                    if (field.getAnnotation(Ignored.class) == null) {
                        field.setAccessible(true);
                        Object fieldValue = field.get(rawController);
                        if (fieldValue == null) {
                            continue;
                        }
                        if (fieldValue instanceof ControllerInterceptor) {
                            NamedControllerInterceptor named = new NamedControllerInterceptor(
                                    controllerPath + "." + field.getName(),
                                    (ControllerInterceptor) fieldValue);
                            named.setController(controller);
                            module.addNamedControllerInterceptor(named);
                            if (logger.isDebugEnabled()) {
                                logger.debug("module '" + module.getPath() + "': add intercetpor ["
                                        + named.getPriority() + "]:" + named.getName() + "="
                                        + fieldValue.getClass().getName());
                            }
                        } else {
                            ArrayList<ControllerInterceptor> list = new ArrayList<ControllerInterceptor>();
                            if (fieldValue.getClass().isArray()
                                    && fieldValue.getClass().getComponentType().isAssignableFrom(
                                            ControllerInterceptor.class)) {
                                ControllerInterceptor[] array = (ControllerInterceptor[]) fieldValue;
                                for (ControllerInterceptor object : array) {
                                    if (object != null) {
                                        list.add(object);
                                    }
                                }
                            } else if (fieldValue instanceof Iterable<?>) {
                                for (Object elem : (Iterable<?>) fieldValue) {
                                    if (elem != null && !(elem instanceof ControllerInterceptor)) {
                                        list.clear();
                                        break;
                                    }
                                    if (elem != null) {
                                        list.add((ControllerInterceptor) elem);
                                    }
                                }
                            }
                            // 具有相同名字的多个拦截器
                            for (ControllerInterceptor controllerInterceptor : list) {
                                NamedControllerInterceptor named = new NamedControllerInterceptor(
                                        controllerPath + "." + field.getName(),
                                        controllerInterceptor);
                                named.setController(controller);
                                module.addNamedControllerInterceptor(named);
                                if (logger.isDebugEnabled()) {
                                    logger.debug("module '" + module.getPath()
                                            + "': add intercetpor ["
                                            + controllerInterceptor.getPriority() + "]="
                                            + named.getName()
                                            + controllerInterceptor.getClass().getName());
                                }
                            }
                        }
                    }
                }
            }
            _clazz = _clazz.getSuperclass();
        }
    }

    protected boolean checkInterceptor(ApplicationContext context, ModuleBean module, Class<?> clazz)
            throws InstantiationException, IllegalAccessException {
        if (ControllerInterceptor.class.isAssignableFrom(clazz)) {
            if (context.getBeansOfType(clazz).size() > 0) {
                return false;
            }
            Interceptor annotation = clazz.getAnnotation(Interceptor.class);
            ControllerInterceptor rawInterceptor = (ControllerInterceptor) clazz.newInstance();
            ControllerInterceptor interceptor = SpringUtils.autowire(rawInterceptor, context);
            NamedControllerInterceptor named = new NamedControllerInterceptor("", interceptor);
            if (annotation != null && StringUtils.isNotBlank(annotation.value())) {
                named.setName(annotation.value());
            } else {
                named.setName(asShortPropertyName(clazz.getSimpleName(), "Interceptor"));
            }
            if (named.getName().indexOf('.') != -1) {
                throw new IllegalArgumentException("请不要使用含有点号(.)的'" + named.getName() + "'作为拦截器的名字");
            }
            module.addNamedControllerInterceptor(named);
            if (Proxy.isProxyClass(interceptor.getClass())) {
                if (logger.isDebugEnabled()) {
                    logger.debug("module '" + module.getPath() + "': add intercetpor ["
                            + interceptor.getPriority() + "]:" + named.getName() + "= proxy of "
                            + clazz.getName());
                }
            } else {
                if (logger.isDebugEnabled()) {
                    logger.debug("module '" + module.getPath() + "': add intercetpor ["
                            + interceptor.getPriority() + "]:" + named.getName() + "="
                            + interceptor.getClass().getName());
                }
            }

            return true;
        }
        return false;
    }

    private boolean checkErrorHandler(ApplicationContext context, ModuleBean module, Class<?> clazz)
            throws InstantiationException, IllegalAccessException {
        if (ControllerErrorHandler.class.isAssignableFrom(clazz)) {
            ControllerErrorHandler errorHandler = SpringUtils.getBean(context, clazz);
            if (errorHandler == null) {
                errorHandler = SpringUtils.createBean(clazz, context);
            }
            module.setErrorHandler(errorHandler);
            if (logger.isInfoEnabled()) {
                logger.info("module '" + module.getPath() + "' add error handler " + errorHandler);
            }
            return true;
        }
        return false;
    }

    private boolean checkResolver(ApplicationContext context, ModuleBean module, Class<?> clazz)
            throws InstantiationException, IllegalAccessException {
        if (Resolver.class.isAssignableFrom(clazz)) {
            if (context.getBeansOfType(clazz).size() > 0) {
                return false;
            }
            Resolver resolver = ResolverInstances.getInstance(clazz);
            module.addCustomerResolver(resolver);
            if (logger.isInfoEnabled()) {
                logger.info("module '" + module.getPath() + "' add parameter resolver " + resolver);
            }
            return true;
        }
        return false;
    }

    protected MultipartResolver initMultipartResolver(ApplicationContext context) {
        MultipartResolver multipartResolver = (MultipartResolver) SpringUtils.getBean(context,
                MultipartResolver.class);
        if (multipartResolver != null) {
            if (logger.isDebugEnabled()) {
                logger.debug("Using MultipartResolver [" + multipartResolver + "]");
            }
        } else {
            multipartResolver = new CommonsMultipartResolver();
            if (logger.isDebugEnabled()) {
                logger.debug("No found MultipartResolver in context, "
                        + "Using MultipartResolver by default [" + multipartResolver + "]");
            }
        }
        return multipartResolver;
    }

    static String asShortPropertyName(String beanName, String suffixToRemove) {
        beanName = org.springframework.util.StringUtils.unqualify(beanName);
        beanName = org.springframework.util.StringUtils.uncapitalize(beanName);
        if (suffixToRemove != null && beanName.endsWith(suffixToRemove)) {
            beanName = beanName.substring(0, beanName.length() - suffixToRemove.length());
        }
        return beanName;
    }
}
